from typing import Any, List, Dict

import mmh3
from aioredis import Redis


class SimpleBloomFilter:
    _factory = {}

    def __new__(cls, *args, **kwargs):
        bf_key = kwargs.get("bf_key")
        if bf_key in cls._factory:
            return cls._factory[bf_key]
        obj = super().__new__(cls)
        cls._factory[bf_key] = obj
        return obj

    def __init__(self,
                 /,
                 bf_key: str,
                 redis: Redis = None,
                 bit_size: int = 1000,
                 hash_count: int = 3
                 ):
        if redis is None:
            from common.g.g import G_REDIS
            self._redis = G_REDIS
        else:
            self._redis = redis
        self._bf_key = bf_key
        self._bit_size = bit_size
        self._hash_count = hash_count
        self.__check()

    def __check(self):
        if not self._bf_key:
            raise ValueError("缺失参数")

    async def add(self, value):
        lua = """
            for i, v in ipairs(ARGV) 
            do
                redis.call("setbit", KEYS[1], v, 1)
            end
        """
        bit_points = self._get_hash_point(value)
        await self._redis.eval(lua, 1, self._bf_key, *bit_points)

    async def madd(self, values: List[Any]):
        for value in values:
            await self.add(value)

    async def exist(self, value):
        lua = """
            local res = {}
            for i, v in ipairs(ARGV)
            do
                local r = redis.call("getbit", KEYS[1], v)
                res[i] = r
                return res
            end
        """
        bit_points = self._get_hash_point(value)
        res = await self._redis.eval(lua, 1, self._bf_key, *bit_points)
        return all(res)

    async def mexist(self, values):
        res = []
        for value in values:
            res.append(await self.exist(value))
        return res

    def _get_hash_point(self, value):
        return [
            mmh3.hash(value, index) % self._bit_size for index in range(self._hash_count)
        ]


if __name__ == '__main__':
    from aioredis import Redis
    import asyncio
    loop = asyncio.get_event_loop()
    r = Redis()
    bf = SimpleBloomFilter(redis=r, bf_key="bf:user:name:count", bit_size=100, hash_count=3)
    bf1 = SimpleBloomFilter(redis=r, bf_key="bf:user:name:count1", bit_size=100, hash_count=3)
    bf2 = SimpleBloomFilter(redis=r, bf_key="bf:user:name:count", bit_size=100, hash_count=3)
    print(bf, bf1, bf2)

    async def test():
        for i in range(80):
            await bf.add(f"num:{i}")
    loop.run_until_complete(test())





